/*
 * 础石OS	-- 设备树
 *
 * Copyright (C) 2022 国科础石(重庆)软件有限公司
 *
 * 作者: Hengxing Wu<wuhengxing@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */

#include <dim-sum/devtree.h>
#include <dim-sum/errno.h>
#include <dim-sum/ioport.h>
#include <dim-sum/printk.h>
#include <dim-sum/string.h>
#include <dim-sum/init.h>
#include <asm/io.h>
#undef pr_debug
#define pr_debug(fmt, ...)
/*
 *ranges地址空间转换
 */
static phys_addr_t addr_translate(struct device_node *node, phys_addr_t in_addr)
{
	phys_addr_t result = in_addr;
	struct device_node *np;
	u32 rlen, na, ns, pna;
	const u32 *ranges;
	u64 base, offset;

	if (!node)
		return result;

	np = node->parent;
	for ( ; np; np = np->parent) {
		/*
		 *如果ranges属性不存在，则跳过此节点
		 */
		if (!dt_find_property(np, "ranges")) {
			pr_debug("no ranges be set, 1:1 translation\n");
			continue;
		}

		/*
		 *当前节点的address-cells,size-cells
		 */
		if (dt_read_u32(np, "#address-cells", &na))
			na = 1;
		if (dt_read_u32(np, "#size-cells", &ns))
			ns = 1;

		/*
		 *父节点的address-cells
		 */
		pna = dt_addr_cells(np);

		/*
		 *地址ranges转换, in_address变换到父节点的地址空间内
		 */
		ranges = dt_get_property(np, "ranges");
		rlen = dt_property_size(np, "ranges");
		if (rlen < (na + pna + ns) * sizeof(u32)) {
			pr_debug("empty ranges; 1:1 translation\n");
			continue;
		}

		base = dt_read_number(ranges, na);
		offset = result - base;
		result = dt_read_number(ranges+na, pna) + offset;
	}

	return result;
}

/*
 *获取设备树节点reg描述的地址空间
 */
phys_addr_t dt_get_addr(struct device_node *node, u64 *size, int index)
{
	u32 reg_l, onesize, addr_cells, size_cells;
	phys_addr_t addr;
	const u32 *reg_v;

	if (!node || index < 0)
		return 0;

	if (dt_find_property(node, "virtual-reg"))
		return 0;

	/**
	 *获取节点#address-cells和#size-cells值
	 */
	addr_cells = dt_addr_cells(node);
	size_cells = dt_size_cells(node);

	/*
	 *获取节点的reg信息
	 */
	reg_v = dt_get_property(node, "reg");
	reg_l = dt_property_size(node, "reg");
	if (!reg_v)
		return 0;

	/*
	 *判断index值是否有效
	 */
	onesize = addr_cells + size_cells;
	if (reg_l < (index + 1)*onesize*sizeof(u32))
		return 0;

	/*
	 *计算得到addr, size值
	 */
	reg_v += index*onesize;
	addr = dt_read_number(reg_v, addr_cells);

	if (size)
		*size = dt_read_number(reg_v+addr_cells, size_cells);

	return addr_translate(node, addr);
}

/*
 *设备树节点寄存器地址映射
 */
void __iomem *dt_reg_map(struct device_node *node, int index)
{
	phys_addr_t pa;
	u64 sz;

	if (!node || index < 0)
		return 0;

	/*
	 *读取reg信息
	 */
	pa = dt_get_addr(node, &sz, index);
	if (!pa || !sz)
		return 0;
	return ioremap(pa, sz);
}

int dt_reg_name_to_index(struct device_node *node, const char *regname)
{
	if (!node || !regname)
		return -EINVAL;

	return dt_match_string(node, "reg-names", regname);
}

void __iomem *dt_reg_map_byname(struct device_node *node, const char *regname)
{
	int index;

	if (!node || !regname)
		return 0;

	index = dt_reg_name_to_index(node, regname);
	if (index < 0)
		return 0;

	return dt_reg_map(node, index);
}

#if 0
void __iomem *dt_reg_request_and_map(struct device_node *node, int index, const char *name)
{
	void __iomem *va;
	phys_addr_t pa;
	u64 sz;

	if (!node || (index < 0) || !name)
		return 0;

	pa = dt_get_addr(node, &sz, index);
	if (!pa || !sz)
		return 0;

	if (!request_mem_region(pa, sz, name))
		return 0;

	va = ioremap(pa, sz);
	if (NULL == va)
		release_mem_region(pa, sz);

	return va;
}

int dt_reg_unmap_and_free(struct device_node *node, void *va, int index)
{
	phys_addr_t pa;
	u64 sz;

	if (!node || index < 0)
		return -EINVAL;

	if (dt_find_property(node, "virtual-reg"))
		return -EINVAL;

	pa = dt_get_addr(node, &sz, index);
	if (!pa || !sz)
		return -EBUSY;

	if (iounmap(va))
		return -EINVAL;

	release_mem_region(pa, sz);

	return 0;
}
#endif

#if 0
/**
 * 读取设备树中的保留内存节点，并保存相应的内存
 */
int __init dt_reserved_memory_init(void)
{
	struct device_node *child, *node;
	phys_addr_t pa;
	u64 sz;
	int pos, ret;

	node = dt_find_node_by_path(DT_PATH_SEPARATOR_STRING "reserved-memory");
	if (!node)
		return 0;

	for_each_child_of_node(child, node) {
		pos = 0;
		while (1) {
			pa = dt_get_addr(child, &sz, pos);
			if (!pa || !sz)
				break;
			pos++;
			ret = phy_pages_reserve(pa, sz);
			pr_debug("reserve: phys=0x%"FMTPADDR " size=%"FMTPSIZE"%s\n",
				pa, sz, (ret) ? " (ignored)" : "");
		}
	}

	dt_node_loosen(node);

	return 0;
}
#endif

